jest-dom
Custom jest matchers to test the state of the DOM



The problem
You want to use jest to write tests that assert various things about the
state of a DOM. As part of that goal, you want to avoid all the repetitive
patterns that arise in doing so. Checking for an element's attributes, its text
content, its css classes, you name it.
This solution
The jest-dom
library provides a set of custom jest matchers that you can use
to extend jest. These will make your tests more declarative, clear to read and
to maintain.
Table of Contents
Installation
This module is distributed via npm which is bundled with node and
should be installed as one of your project's devDependencies
:
npm install --save-dev jest-dom
Usage
Import jest-dom/extend-expect
once (for instance in your tests setup file)
and you're good to go:
import 'jest-dom/extend-expect'
Alternatively, you can selectively import only the matchers you intend to use,
and extend jest's expect
yourself:
import {toBeInTheDocument, toHaveClass} from 'jest-dom'
expect.extend({toBeInTheDocument, toHaveClass})
Note: when using TypeScript, this way of importing matchers won't provide the
necessary type definitions. More on this here.
Custom matchers
jest-dom
can work with any library or framework that returns DOM elements from queries. The custom matcher examples below demonstrate using document.querySelector
and dom-testing-library for querying DOM elements.
toBeDisabled
toBeDisabled()
This allows you to check whether an element is disabled from the user's perspective.
It matches if the element is a form control and the disabled
attribute is
specified on this element or the element is a descendant of a form element
with a disabled
attribute.
According to the specification, the following elements can be actually disabled:
button
, input
, select
, textarea
, optgroup
, option
, fieldset
.
Examples
<button data-testid="button" type="submit" disabled>submit</button>
<fieldset disabled><input type="text" data-testid="input" /></fieldset>
<a href="..." disabled>link</a>
Using document.querySelector
expect(document.querySelector('[data-testid="button"]')).toBeDisabled()
expect(document.querySelector('[data-testid="input"]')).toBeDisabled()
expect(document.querySelector('a')).not.toBeDisabled()
Using dom-testing-library
expect(getByTestId(container, 'button')).toBeDisabled()
expect(getByTestId(container, 'input')).toBeDisabled()
expect(getByText(container, 'link')).not.toBeDisabled()
toBeEmpty
toBeEmpty()
This allows you to assert whether an element has content or not.
Examples
<span data-testid="not-empty"><span data-testid="empty"></span></span>
Using document.querySelector
expect(document.querySelector('[data-testid="empty"]').toBeEmpty()
expect(document.querySelector('[data-testid="not-empty"]').not.toBeEmpty()
Using dom-testing-library
expect(queryByTestId(container, 'empty')).toBeEmpty()
expect(queryByTestId(container, 'not-empty')).not.toBeEmpty()
toBeInTheDocument
toBeInTheDocument()
This allows you to assert whether an element is present in the document or not.
Examples
<span data-testid="html-element"><span>Html Element</span></span>
<svg data-testid="svg-element"></svg>
Using document.querySelector
const htmlElement = document.querySelector('[data-testid="html-element"]')
const svgElement = document.querySelector('[data-testid="svg-element"]')
const nonExistantElement = document.querySelector('does-not-exist')
const detachedElement = document.createElement('div')
expect(htmlElement).toBeInTheDocument()
expect(svgElement).toBeInTheDocument()
expect(detacthedElement).not.toBeInTheDocument()
expect(nonExistantElement).not.toBeInTheDocument()
Using dom-testing-library
expect(
queryByTestId(document.documentElement, 'html-element'),
).toBeInTheDocument()
expect(
queryByTestId(document.documentElement, 'svg-element'),
).toBeInTheDocument()
expect(
queryByTestId(document.documentElement, 'does-not-exist'),
).not.toBeInTheDocument()
Note: This matcher does not find detached elements. The element must be added to the document to be found by toBeInTheDocument. If you desire to search in a detached element please use: toContainElement
toBeVisible
toBeVisible()
This allows you to check if an element is currently visible to the user.
An element is visible if all the following conditions are met:
- it does not have its css property
display
set to none
- it does not have its css property
visibility
set to either hidden
or
collapse
- it does not have its css property
opacity
set to 0
- its parent element is also visible (and so on up to the top of the DOM tree)
Examples
<div data-testid="zero-opacity" style="opacity: 0">Zero Opacity Example</div>
<div data-testid="visibility-hidden" style="visibility: hidden">Visibility Hidden Example</div>
<div data-testid="display-none" style="display: none">Display None Example</div>
<div style="opacity: 0"><span data-testid="hidden-parent">Hidden Parent Example</span></div>
<div data-testid="visible">Visible Example</div>
Using document.querySelector
expect(document.querySelector('[data-testid="zero-opacity"]'])).not.toBeVisible()
expect(document.querySelector('[data-testid="visibility-hidden"]'])).not.toBeVisible()
expect(document.querySelector('[data-testid="display-none"]'])).not.toBeVisible()
expect(document.querySelector('[data-testid="hidden-parent"]'])).not.toBeVisible()
expect(document.querySelector('[data-testid="visible"]'])).toBeVisible()
Using dom-testing-library
expect(getByText(container, 'Zero Opacity Example')).not.toBeVisible()
expect(getByText(container, 'Visibility Hidden Example')).not.toBeVisible()
expect(getByText(container, 'Display None Example')).not.toBeVisible()
expect(getByText(container, 'Hidden Parent Example')).not.toBeVisible()
expect(getByText(container, 'Visible Example')).toBeVisible()
toContainElement
toContainElement(element: HTMLElement | SVGElement | null)
This allows you to assert whether an element contains another element as a descendant or not.
Examples
<span data-testid="ancestor"><span data-testid="descendant"></span></span>
Using document.querySelector
const ancestor = document.querySelector('[data-testid="ancestor"]')
const descendant = document.querySelector('[data-testid="descendant"]')
const nonExistantElement = document.querySelector(
'[data-testid="does-not-exist"]',
)
expect(ancestor).toContainElement(descendant)
expect(descendant).not.toContainElement(ancestor)
expect(ancestor).not.toContainElement(nonExistantElement)
Using dom-testing-library
const {queryByTestId} = render()
const ancestor = queryByTestId(container, 'ancestor')
const descendant = queryByTestId(container, 'descendant')
const nonExistantElement = queryByTestId(container, 'does-not-exist')
expect(ancestor).toContainElement(descendant)
expect(descendant).not.toContainElement(ancestor)
expect(ancestor).not.toContainElement(nonExistantElement)
toContainHTML
toContainHTML(htmlText: string)
Assert whether a string representing a HTML element is contained in another element:
Examples
<span data-testid="parent"><span data-testid="child"></span></span>
Using document.querySelector
expect(document.querySelector('[data-testid="parent"]')).toContainHTML(
'<span data-testid="child"></span>',
)
Using dom-testing-library
expect(getByTestId(container, 'parent')).toContainHTML(
'<span data-testid="child"></span>',
)
Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised.
It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended.
It should not be used to check DOM structure that you control. Please use toContainElement
instead.
toHaveAttribute
toHaveAttribute(attr: string, value?: string)
This allows you to check whether the given element has an attribute or not. You
can also optionally check that the attribute has a specific expected value.
Examples
<button data-testid="ok-button" type="submit" disabled>ok</button>
Using document.querySelector
const button = document.querySelector('[data-testid="ok-button"]')
expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')
Using dom-testing-library
const button = getByTestId(container, 'ok-button')
expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')
toHaveClass
toHaveClass(...classNames: string[])
This allows you to check whether the given element has certain classes within its
class
attribute.
You must provide at least one class, unless you are asserting that an element
does not have any classes.
Examples
<button data-testid="delete-button" class="btn extra btn-danger">Delete item</button>
<button data-testid="no-classes">No Classes</button>
Using document.querySelector
const deleteButton = document.querySelector('[data-testid="delete-button"]')
const noClasses = document.querySelector('[data-testid="no-classes"]')
expect(deleteButton).toHaveClass('extra')
expect(deleteButton).toHaveClass('btn-danger btn')
expect(deleteButton).toHaveClass('btn-danger', 'btn')
expect(deleteButton).not.toHaveClass('btn-link')
expect(noClasses).not.toHaveClass()
Using dom-testing-library
const deleteButton = getByTestId(container, 'delete-button')
const noClasses = getByTestId(container, 'no-classes')
expect(deleteButton).toHaveClass('extra')
expect(deleteButton).toHaveClass('btn-danger btn')
expect(deleteButton).toHaveClass('btn-danger', 'btn')
expect(deleteButton).not.toHaveClass('btn-link')
expect(noClasses).not.toHaveClass()
toHaveFocus
toHaveFocus()
This allows you to assert whether an element has focus or not.
Examples
<div><input type="text" data-testid="element-to-focus" /></div>
Using document.querySelector
const input = document.querySelector(['data-testid="element-to-focus"')
input.focus()
expect(input).toHaveFocus()
input.blur()
expect(input).not.toHaveFocus()
Using dom-testing-library
const input = queryByTestId(container, 'element-to-focus')
fireEvent.focus(input)
expect(input).toHaveFocus()
fireEvent.blur(input)
expect(input).not.toHaveFocus()
toHaveStyle
toHaveStyle(css: string)
This allows you to check if a certain element has some specific css properties
with specific values applied. It matches only if the element has all the
expected properties applied, not just some of them.
Examples
<button data-testid="delete-button" style="display: none; color: red">
Delete item
</button>
Using document.querySelector
const input = document.querySelector(['data-testid="delete-button"')
expect(button).toHaveStyle('display: none')
expect(button).toHaveStyle(`
color: red;
display: none;
`)
expect(button).not.toHaveStyle(`
display: none;
color: blue;
`)
Using dom-testing-library
const button = getByTestId(container, 'delete-button')
expect(button).toHaveStyle('display: none')
expect(button).toHaveStyle(`
color: red;
display: none;
`)
expect(button).not.toHaveStyle(`
display: none;
color: blue;
`)
This also works with rules that are applied to the element via a class name for
which some rules are defined in a stylesheet currently active in the document.
The usual rules of css precedence apply.
toHaveTextContent
toHaveTextContent(text: string | RegExp)
This API allows you to check whether the given element has a text content or not.
Examples
<span data-testid="count-value">2</span>
Using document.querySelector
const button = document.querySelector('[data-testid="count-value"]')
expect(content).toHaveTextContent('2')
expect(content).toHaveTextContent(/^2$/)
expect(content).not.toHaveTextContent('21')
Using dom-testing-library
const content = getByTestId(container, 'count-value')
expect(content).toHaveTextContent('2')
expect(content).toHaveTextContent(/^2$/)
expect(content).not.toHaveTextContent('21')
Deprecated matchers
toBeInTheDOM
toBeInTheDOM()
This allows you to check whether a value is a DOM element, or not.
Contrary to what its name implies, this matcher only checks that you passed to
it a valid DOM element. It does not have a clear definition of that "the DOM"
is. Therefore, it does not check wether that element is contained anywhere.
This is the main reason why this matcher is deprecated, and will be removed in
the next major release. You can follow the discussion around this decision in
more detail here.
As an alternative, you can use toBeInTheDocument
or toContainElement
. Or if you just want to check if a
value is indeed an HTMLElement
you can always use some of
jest's built-in matchers:
expect(document.querySelector('.ok-button')).toBeInstanceOf(HTMLElement)
expect(document.querySelector('.cancel-button')).toBeThruthy()
Note: The differences between toBeInTheDOM
and toBeInTheDocument
are
significant. Replacing all uses of toBeInTheDOM
with toBeInTheDocument
will likely cause unintended consequences in your tests. Please make sure when
replacing toBeInTheDOM
to read through the documentation of the proposed
alternatives to see which use case works better for your needs.
Inspiration
This whole library was extracted out of Kent C. Dodds' dom-testing-library,
which was in turn extracted out of react-testing-library.
The intention is to make this available to be used independently of these other
libraries, and also to make it more clear that these other libraries are
independent from jest, and can be used with other tests runners as well.
Other Solutions
I'm not aware of any, if you are please make a pull request and add it
here!
Guiding Principles
The more your tests resemble the way your software is used, the more confidence they can give you.
This library follows the same guiding principles as its mother library dom-testing-library.
Go check them out
for more details.
Additionally, with respect to custom DOM matchers, this library aims to maintain
a minimal but useful set of them, while avoiding bloating itself with merely
convenient ones that can be easily achieved with other APIs. In general, the
overall criteria for what is considered a useful custom matcher to add to this
library, is that doing the equivalent assertion on our own makes the test code
more verbose, less clear in its intent, and/or harder to read.
Contributors
Thanks goes to these people (emoji key):
This project follows the all-contributors specification.
Contributions of any kind welcome!
LICENSE
MIT